/* @flow */
//创建组件实例，初始化其数据、属性、事件等
import config from "../config";
import { initProxy } from "./proxy";
import { initState } from "./state";
import { initRender } from "./render";
import { initEvents } from "./events";
import { mark, measure } from "../util/perf";
import { initLifecycle, callHook } from "./lifecycle";
import { initProvide, initInjections } from "./inject";
import { extend, mergeOptions, formatComponentName } from "../util/index";

let uid = 0;

export function initMixin(Vue: Class<Component>) {
  //合并配置，调用一些初始化函数，触发生命周期钩子函数，调用$mount开启下一个阶段。
  Vue.prototype._init = function (options?: Object) {
    //把Vue实例赋值给变量vm
    const vm: Component = this;
    vm._uid = uid++;
    let startTag, endTag;
    if (process.env.NODE_ENV !== "production" && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`;
      endTag = `vue-perf-end:${vm._uid}`;
      mark(startTag);
    }
    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    if (options && options._isComponent) {
      // 针对子组件的事件处理逻辑
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      // 初始化内部组件
      initInternalComponent(vm, options);
    } else {
      /**
       * 用户传递的options选项与当前构造函数的options属性及其父级构造函数的options属性进行合并
       * 得到一个新的options选项赋值给$options属性，并将$options属性挂载到Vue实例上
       *
       */
      //resolveConstructorOptions(vm.constructor) 相当于 Vue.options， 在 initGlobalAPI(Vue) 的时候定义了这个值，代码在 src/core/global-api/index.js 中,是Vue构造函数自身的配置项
      //Vue.options.components = {}Vue.options.directives = {}Vue.options.filters = {} 和一些内置组件放到Vue.options.components
      //mergeOptions 这个函数，它的定义在 src/core/util/options.js 中：
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== "production") {
      initProxy(vm); //代理
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    //通过调用一些初始化函数来为Vue实例初始化一些属性，事件，响应式数据
    vm._self = vm;
    initLifecycle(vm); //$parent,$root,$children,$refs  初始化生命周期
    //父组件给子组件的注册事件中，把自定义事件传给子组件，在子组件实例化的时候进行初始化；而浏览器原生事件是在父组件中处理。
    //换句话说：实例初始化阶段调用的初始化事件函数initEvents实际上初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件。
    initEvents(vm); //处理父组件传递的事件和回调  初始化事件
    initRender(vm); //$slots,$scopedSlots,_c,$createElement  初始化渲染
    callHook(vm, "beforeCreate"); // 调用生命周期钩子函数
    /**
     * provide 和 inject 选项绑定的数据不是响应式的。
     * provide: {
            foo: 'bar'
        },
     * inject选项：
     * 1、一个字符串数组:["foo"]
     * 2、一个对象，对象的 key 是本地的绑定名，value 是在可用的注入内容中搜索用的 key (字符串或 Symbol)或一个对象.
     *      该对象的from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol).default 属性是降级情况下使用的 value
     * {"foo2":"foo"}
     * {"foo2":{from:"foo",default: () => [1, 2, 3]}}
     * 为什么在init函数中调用initInjections函数和initProvide函数之间穿插一个initState函数呢？
     * 因为provide选项注入的值可以作为数据入口。 如 inject:["foo"] data(){return {bar :this.foo}}
     * 这里所说的数据就是我们通常所写data、props、watch、computed及method，所以inject选项接收到注入的值有可能被以上这些数据所使用到，所以在初始化完inject后需要先初始化这些数据，
       然后才能再初始化provide，所以在调用initInjections函数对inject初始化完之后需要先调用initState函数对数据进行初始化，最后再调用initProvide函数对provide进行初始化。
     *
     */

    initInjections(vm); // resolve injections before data/props   // 获取注入数据
    //initState函数就是用来初始化,初始化props，methods，data，computed，watch
    initState(vm);
    initProvide(vm); // resolve provide after data/props // 提供数据注入
    callHook(vm, "created"); // 调用生命周期钩子函数

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== "production" && config.performance && mark) {
      vm._name = formatComponentName(vm, false);
      mark(endTag);
      measure(`vue ${vm._name} init`, startTag, endTag);
    }

    //如果没有传入el选项，则不进入下一个生命周期阶段，需要用户手动执行vm.$mount方法才进入下一个生命周期阶段。
    if (vm.$options.el) {
      debugger; //$mount platforms/web/entry-runtime-with-compiler
      vm.$mount(vm.$options.el);
    }
  };
}

export function initInternalComponent(
  vm: Component,
  options: InternalComponentOptions
) {
  const opts = (vm.$options = Object.create(vm.constructor.options));
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;

  const vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  //vm.$options._parentListeners会拿到父组件自定义的事件。而带有自定义事件的组件会执行updateComponentListeners函数。
  opts._parentListeners = vnodeComponentOptions.listeners; //拿到自定义事件的回调
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;

  if (options.render) {
    opts.render = options.render;
    opts.staticRenderFns = options.staticRenderFns;
  }
}

export function resolveConstructorOptions(Ctor: Class<Component>) {
  let options = Ctor.options;
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super);
    const cachedSuperOptions = Ctor.superOptions;
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions;
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor);
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions);
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
      if (options.name) {
        options.components[options.name] = Ctor;
      }
    }
  }
  return options;
}

function resolveModifiedOptions(Ctor: Class<Component>): ?Object {
  let modified;
  const latest = Ctor.options;
  const sealed = Ctor.sealedOptions;
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {};
      modified[key] = latest[key];
    }
  }
  return modified;
}
